﻿using JavaVirtualMachine.ConstantPoolItems;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;

namespace JavaVirtualMachine
{
    public static class ClassFileManager
    {
        private static Dictionary<string, (string path, int classFileIndex, bool isStaticLoaded)> StrToClassFileInfo;
        private static Dictionary<string, (string path, int classFileIndex, bool isStaticLoaded)> FileNameToClassFileInfo;
        private static List<ClassFile> _classFiles = new();
        public static IReadOnlyList<ClassFile> ClassFiles => _classFiles;

        private static string RuntimePath;
        private static string[] AndroidSdkPath;
        private static string[] srcPath;
        /*
        public static void InitDictionary(string runtimePath, params string[] otherPaths)
        {
            StrToClassFileInfo = new Dictionary<string, (string, int, bool)>();
            FileNameToClassFileInfo = new Dictionary<string, (string, int, bool)>();
            foreach (string path in otherPaths)
            {
                string[] files = Directory.GetFiles(path, "*.class", SearchOption.AllDirectories);
                foreach (string filePath in files)
                {
                    string fileName = filePath.Substring(path.Length);
                    fileName = fileName.Substring(1, fileName.Length - 7); //Remove ".class"
                    string key = fileName.Replace('\\', '/');
                    StrToClassFileInfo.Add(key, (path, -1, false));
                    string name = Path.GetFileName(fileName);
                    //FileNameToClassFileInfo.Add(name, (filePath.Substring(0, filePath.Length - 6), -1, false));
                }
            }
            RuntimePath = runtimePath;
            srcPath = otherPaths;
        }*/

        public static void InitDictionary(string runtimePath, string path,string[] files,string[] androidPath)
        {
            StrToClassFileInfo = new Dictionary<string, (string, int, bool)>();
            FileNameToClassFileInfo = new Dictionary<string, (string, int, bool)>();

            foreach (string filePath in files)
            {
                StrToClassFileInfo.Add(filePath, (path, -1, false));
            }
            AndroidSdkPath = androidPath;
            RuntimePath = runtimePath;
            srcPath =new string[] { path };
        }

        public static int GetClassFileIndex(CClassInfo cClassInfo)
        {
            return GetClassFileIndex(cClassInfo.Name);
        }

        public static int GetClassFileIndex(int classObjAddr)
        {
            HeapObject classObj = Heap.GetObject(classObjAddr);
            string name = JavaHelper.ReadJavaString(classObj.GetField(2));
            return GetClassFileIndex(name);
        }

        public static MethodInfo? TryGetClassFileIndex(Stack<MethodFrame> MethodFrameStack, System.Memory<int> GlobalStack,string key, out int classFileIndex)
        {
            try
            {
                classFileIndex = GetClassFileIndex(key);
                return null;
            }
            catch (DirectoryNotFoundException)
            {
                int exCFileIdx = GetClassFileIndex("java/lang/ClassNotFoundException");
                ClassFile exCFile = ClassFiles[exCFileIdx];
                MethodInfo initMethodInfo = exCFile.MethodDictionary[("<init>", "(Ljava/lang/String;)V")];
                string message = $"Could not find class {key}";
                int messageAddr = JavaHelper.CreateJavaStringLiteral(message);
                classFileIndex = default;
                UnityEngine.Debug.LogWarning(message);
                return Executor.ThrowJavaException(MethodFrameStack, GlobalStack, initMethodInfo, messageAddr);
            }
        }

        public static int GetClassFileIndex(string key)
        {
            if (key.Last() == ';')
                key = key.Substring(1, key.Length - 2);
            key = key.Replace('.', '/');

            //todo: check permissions

            if (StrToClassFileInfo.TryGetValue(key, out (string path, int classFileIndex, bool isStaticLoaded) classFileEntry))
            {
                bool isLoaded = classFileEntry.classFileIndex != -1;
                if (!isLoaded)
                {
                    //ClassFile classFile = new ClassFile(StrToClassFileInfo[key].path +"/"+ key + ".class");
                    ClassFile classFile = new ClassFile(JarFile.GetClass(key));
                    _classFiles.Add(classFile);

                    int index = _classFiles.Count - 1;
                    StrToClassFileInfo[key] = (classFileEntry.path, index, false);
                    return index;
                }
                else
                {
                    return classFileEntry.classFileIndex;
                }
            }
            else
            {
                string path = RuntimePath;
                //Java class file
                string filePath = RuntimePath + key + ".class";

                if (!File.Exists(filePath))
                {
                    for (int i = 0; i < AndroidSdkPath.Length; i++)
                    {
                        filePath = AndroidSdkPath[i] + key + ".class";
                        if (File.Exists(filePath))
                        {
                            path = AndroidSdkPath[i];
                            break;
                        }
                    }
                    
                }

                ClassFile classFile = new ClassFile(filePath);
                _classFiles.Add(classFile);

                int index = _classFiles.Count - 1;
                if(!StrToClassFileInfo.ContainsKey(key))
                     StrToClassFileInfo.Add(key, (path, index, false));
                return index;
            }
        }

        internal static bool TryGetValue(string name)
        {
            if (StrToClassFileInfo.ContainsKey(name))
            {
                return true;
            }
            return false;

        }

        public static ClassFile GetClassFile(string name)
        {
            return ClassFiles[GetClassFileIndex(name)];
        }

        public static MethodInfo? InitializeClass(string key, Stack<MethodFrame> MethodFrameStack, System.Memory<int> GlobalStack)
        {
            ClassFile cFile = GetClassFile(key);
            ClassFile tFile = cFile;
            while (cFile != null)
            {
                if (!StrToClassFileInfo[cFile.Name].isStaticLoaded)
                {
                    StrToClassFileInfo[cFile.Name] = (StrToClassFileInfo[cFile.Name].path, StrToClassFileInfo[cFile.Name].classFileIndex, true);
                    
                    if (cFile.MethodDictionary.TryGetValue(("<clinit>", "()V"), out MethodInfo classInitMethod))
                    {
                        return Executor.RunJavaFunction(MethodFrameStack, GlobalStack, classInitMethod);
                    }
                }
                cFile = cFile.SuperClass;
            }

            while (tFile != null)
            {
                ClassFile[] interfaceClass = tFile.InterfaceClass;
                if(interfaceClass != null)
                {
                    for (int i = 0; i < interfaceClass.Length; i++)
                    {
                        ClassFile iFile = interfaceClass[i];
                        if (!StrToClassFileInfo[iFile.Name].isStaticLoaded)
                        {
                            StrToClassFileInfo[iFile.Name] = (StrToClassFileInfo[iFile.Name].path, StrToClassFileInfo[iFile.Name].classFileIndex, true);

                            if (iFile.MethodDictionary.TryGetValue(("<clinit>", "()V"), out MethodInfo classInitMethod))
                            {
                                return Executor.RunJavaFunction(MethodFrameStack, GlobalStack, classInitMethod);
                            }
                        }
                    }
                    
                }
                
                tFile = tFile.SuperClass;
            }

            return null;
        }

        public static MethodInfo? GetMethodInfo(ClassFile cFile,string name,string descriptor)
        {
            while (cFile != null)
            {
                if (cFile.MethodDictionary.TryGetValue((name, descriptor), out MethodInfo classInitMethod))
                {
                    return classInitMethod;
                }
                cFile = cFile.SuperClass;
            }

            return null;
        }
    }
}
